﻿/*****************************************************************************
Copyright (c) 2011 SmartVault, Inc.

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
******************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using SmartVault.Net;
using SmartVault.Net.Http;
using System.IO;
using System.Collections.Specialized;
using SmartVault.Core;
using SmartVault.Proto;

namespace SmartVault.Rest
{
    /// <summary>
    /// DelegatedProtocol
    /// 
    /// Represents a wrapper around the user driven portion of the REST protcool.
    /// An access token is used to access this portion of the protocol.
    /// </summary>
    public class DelegatedProtocol
    {
        private UserCfg UserCfg { get; set; }

        private Uri RootUri { get { return this.UserCfg.RootUri;  } }
        private string Token { get { return this.UserCfg.AccessToken; } }
        private MediaType Accept { get { return this.UserCfg.MediaType;  } }
        private Encoding Encoding { get { return this.UserCfg.Encoding;  } }
        private NetWebClient Client { get; set; }


        public string UserEmail { get { return UserCfg.UserName; } }
        public string UserId { get { return UserCfg.UserId;  } }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="cfg"></param>
        public DelegatedProtocol(UserCfg cfg)
        {
            this.UserCfg = cfg;

            //Console.WriteLine("{0} => {1}", this.UserName, this.Token);

            this.Client = new NetWebClient(new WebRequestInfo(RootUri, UserEmail, Token, Accept, Encoding));
        }

        public string GetMimeType(string localFileName)
        {
            string mimeType = "application/unknown";
            string ext = System.IO.Path.GetExtension(localFileName).ToLower();
            Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);
            if (regKey != null && regKey.GetValue("Content Type") != null)
                mimeType = regKey.GetValue("Content Type").ToString();
            return mimeType;
        }

        #region Generic Integration and Common Calls
        /// <summary>
        /// Copy a file or folder from one location to another
        /// </summary>
        /// <param name="src">Source path</param>
        /// <param name="dst">Destination path</param>
        /// <returns></returns>
        public NodeResponse Copy(AbsolutePath src, AbsolutePath dst)
        {
            ChangeNodeRequest.Builder request = new ChangeNodeRequest.Builder();
            CopyNodeRequest.Builder bld = new CopyNodeRequest.Builder();
            bld.DstUri = dst.ToString();
            request.Copy = bld.Build();
            ClientNetContext ctx =
                this.Client.Open(src.ToString(), HttpMethods.POST.ToString(), null);

            ctx.WritePayload(request.Build());
            return ctx.ReadPayload<NodeResponse>();
        }

        /// <summary>
        /// Copy a file or folder from one location to another
        /// </summary>
        /// <param name="src">Source path</param>
        /// <param name="rqst">Destination Information</param>
        /// <returns></returns>
        public NodeResponse Copy(AbsolutePath src, CopyNodeRequest rqst)
        {
            ChangeNodeRequest.Builder request = new ChangeNodeRequest.Builder();
            request.Copy = rqst;

            ClientNetContext ctx =
                this.Client.Open(src.ToString(), HttpMethods.POST.ToString(), null);

            ctx.WritePayload(request.Build());
            return ctx.ReadPayload<NodeResponse>();
        }

        /// <summary>
        /// Move a file or folder from one location to another
        /// </summary>
        /// <param name="src">Source Path</param>
        /// <param name="dst">Destination Path</param>
        /// <returns></returns>
        public NodeResponse Move(AbsolutePath src, AbsolutePath dst)
        {
            ChangeNodeRequest.Builder request = new ChangeNodeRequest.Builder();
            MoveNodeRequest.Builder bld = new MoveNodeRequest.Builder();
            bld.DstUri = dst.ToString();
            request.Move = bld.Build();
            ClientNetContext ctx =
                this.Client.Open(src.ToString(), HttpMethods.POST.ToString(), null);

            ctx.WritePayload(request.Build());
            return ctx.ReadPayload<NodeResponse>();
        }

        /// <summary>
        /// Move a file or folder from one location to another
        /// </summary>
        /// <param name="src">Source Path</param>
        /// <param name="rqst">Destination Info</param>
        /// <returns></returns>
        public NodeResponse Move(AbsolutePath src, MoveNodeRequest rqst)
        {
            ChangeNodeRequest.Builder request = new ChangeNodeRequest.Builder();
            request.Move = rqst;
            ClientNetContext ctx =
                this.Client.Open(src.ToString(), HttpMethods.POST.ToString(), null);

            ctx.WritePayload(request.Build());
            return ctx.ReadPayload<NodeResponse>();
        }

        /// <summary>
        /// Browse to a location in the hierarchy
        /// </summary>
        /// <param name="remotePath">Path</param>
        /// <param name="children">Depth of children to return</param>
        /// <returns></returns>
        public NodeResponse Navigate(AbsolutePath remotePath, int children)
        {
            NodeOptions options = new NodeOptions();
            options.Children = children;
            return Navigate(remotePath, options);
        }

        /// <summary>
        /// Browse to a location in the hierarchy
        /// </summary>
        /// <param name="remotePath">Path</param>
        /// <param name="options">Options</param>
        /// <returns></returns>
        public NodeResponse Navigate(AbsolutePath remotePath, NodeOptions options)
        {
            return this.Client.Open(remotePath.ToString(), HttpMethods.GET.ToString(), options.GetParameters()).ReadPayload<NodeResponse>();
        }

        /// <summary>
        /// Add a user to a particular account
        /// </summary>
        /// <param name="remotePath">Path to the account</param>
        /// <returns></returns>
        public NodeResponse AddUser(AbsolutePath remotePath)
        {
            ClientNetContext ctx = this.Client.Open(remotePath.ToString(), HttpMethods.PUT.ToString(), null);

            ctx.WritePayload(new AddUserRequest());
            return ctx.ReadPayload<NodeResponse>();
        }

        /// <summary>
        /// Update a file or a folder
        /// </summary>
        /// <param name="remotePath">Path of file folder</param>
        /// <param name="options">Options</param>
        /// <param name="request">Changes to make</param>
        /// <returns></returns>
        public NodeResponse Update(AbsolutePath remotePath, NodeOptions options, ChangeNodeRequest request)
        {
            ClientNetContext ctx = this.Client.Open(remotePath.ToString(), HttpMethods.POST.ToString(),
                                                    options.GetParameters());

            ctx.WritePayload(request);

            return ctx.ReadPayload<NodeResponse>();
        }

        /// <summary>
        /// Upload a file
        /// </summary>
        /// <param name="parentPath">Parent path</param>
        /// <param name="request">Information about file</param>
        /// <param name="localPath">Local file to upload</param>
        /// <returns></returns>
        public NodeResponse PostFile(AbsolutePath parentPath, UploadFileRequest request, string localPath)
        {
            string type = GetMimeType(localPath);
            string fileName = Path.GetFileName(localPath);

            using (Stream input = File.OpenRead(fileName))
            {
                return PostFile(parentPath, request, input);
            }
        }

        /// <summary>
        /// Upload a file
        /// </summary>
        /// <param name="parentPath">Parent path</param>
        /// <param name="request">Information about file</param>
        /// <param name="stream">Stream to upload</param>
        /// <returns></returns>
        public NodeResponse PostFile(AbsolutePath parentPath, UploadFileRequest request, Stream stream)
        {
            ClientNetContext ctx = this.Client.Open(parentPath.ToString(), HttpMethods.POST.ToString(), null);

            ctx.WritePayload(request, stream);

            return ctx.ReadPayload<NodeResponse>();
        }

        /// <summary>
        /// Delete file
        /// </summary>
        /// <param name="path">Path to file</param>
        public void DeleteFile(AbsolutePath path)
        {
            ClientNetContext ctx = this.Client.Open(path.ToString(), HttpMethods.DELETE.ToString(), null);

            ctx.ReadPayload<DeleteFileResponse>();
        }

        /// <summary>
        /// Create a folder
        /// </summary>
        /// <param name="path">Path to folder</param>
        /// <param name="req">Request</param>
        /// <returns></returns>
        public NodeResponse CreateFolder(AbsolutePath path, CreateFolderRequest req)
        {
            ClientNetContext ctx =
                this.Client.Open(path.ToString(), HttpMethods.PUT.ToString(), null);

            ctx.WritePayload(req);

            return ctx.ReadPayload<NodeResponse>();
        }

        /// <summary>
        /// Create a folder
        /// </summary>
        /// <param name="parentPath">Path to parent</param>
        /// <param name="name">Name of folder</param>
        /// <param name="req">Request</param>
        /// <returns></returns>
        public NodeResponse CreateFolder(AbsolutePath parentPath, string name, CreateFolderRequest req)
        {
            return CreateFolder(parentPath.Append(name), req);
        }

        /// <summary>
        /// Read File
        /// </summary>
        /// <param name="remotePath">Path to file</param>
        /// <returns></returns>
        public NetAttachmentReader ReadFile(AbsolutePath remotePath)
        {
            ClientNetContext ctx = this.Client.Open(remotePath.ToString(), HttpMethods.GET.ToString(), null);
            return ctx.ReadAttachment();
        }

        /// <summary>
        /// Download File
        /// </summary>
        /// <param name="remotePath">Remove path</param>
        /// <param name="localPath">Local path</param>
        /// <param name="overwrite">Overwrite local file if exists?</param>
        public void DownloadFile(AbsolutePath remotePath, string localPath, bool overwrite)
        {
            if (File.Exists(localPath))
            {
                if (overwrite)
                {
                    File.Delete(localPath);
                }
                else
                {
                    throw new InvalidOperationException();
                }
            }

            int b = -1;

            NetAttachmentReader rdr = ReadFile(remotePath);

            using (Stream output = File.Create(localPath, 1024))
            {
                while ((b = rdr.Stream.ReadByte()) != -1)
                {
                    output.WriteByte((byte)b);
                }
            }
        }
        #endregion

        #region Application Integration Specific Calls
        /// <summary>
        /// Given an ApplicationType and Application Id return the path to the company
        /// </summary>
        /// <param name="applicationType"></param>
        /// <param name="applicationId"></param>
        /// <returns></returns>
        public AbsolutePath GetCompanyPath(string applicationType, string applicationId)
        {
            return new AbsolutePath(Constants.AppPath).Append(applicationType).Append(applicationId);
        }

        /// <summary>
        /// Determine if a company exists
        /// </summary>
        /// <param name="applicationType"></param>
        /// <param name="applicationId"></param>
        /// <returns></returns>
        public bool DoesCompanyExist(string applicationType, string applicationId)
        {
            NameValueCollection nv = new NameValueCollection();
            nv[Constants.ChildrenParameter] = 1.ToString();

            AbsolutePath pth = new AbsolutePath("/nodes/app").Append(applicationType);

            NodeResponse nodeSimple = this.Client.Open(pth.ToString(), HttpMethods.GET.ToString(), nv).ReadPayload<NodeResponse>();

            bool found = false;

            AbsolutePath myCompany = pth.Append(applicationId);

            foreach (NodeResponse.Types.Message node in nodeSimple.Message.ChildrenList)
            {
                found = (node.Uri == myCompany.ToString());
                if (found) break;
            }

            return found;
        }

        /// <summary>
        /// Provision Company
        /// </summary>
        /// <param name="provisionAt">/nodes/pth based path to provision the company at</param>
        /// <param name="name">Name of the company</param>
        /// <param name="applicationType"></param>
        /// <param name="applicationId"></param>
        /// <returns></returns>
        public NodeResponse ProvisionCompany(AbsolutePath provisionAt, string name, string applicationType, string applicationId)
        {
            CreateAppContextRequest.Builder app = CreateAppContextRequest.CreateBuilder();
            app.Name = name;
            app.ProvisionAt = provisionAt.ToString();

            AbsolutePath pth = new AbsolutePath(Constants.AppPath).Append(applicationType).Append(applicationId);

            ClientNetContext ctx =
                this.Client.Open(pth.ToString(), HttpMethods.PUT.ToString(), null);

            ctx.WritePayload(app.Build());

            return ctx.ReadPayload<NodeResponse>();
        }
        #endregion
    }
}
